using System;
using System.Collections.Generic;
using System.Text;
using MacomberMap.Common.Components;
using System.Xml;
using MacomberMap.Common.Types;
using MacomberMap.Common.Database_Interface.CIM.Components;
using MacomberMap.Common.Internals;
using MacomberMap.Common.User_Interfaces.Components;
using MacomberMap.Common.Database_Interface.CIM;

namespace MacomberMap.Loader.Export
{
    /// <summary>
    /// This class holds information on a contingency grouping
    /// </summary>
    public class MM_Contingency_Grouping: MM_Serializable
    {
        #region Variable declarations
        /// <summary>The rdf:ID of the contingency grouping</summary>
        public MM_rdfID rdfID;
        
        /// <summary>The name of the grouping</summary>
        public String Name;

        /// <summary>The description of the grouping</summary>
        public String Description;

        /// <summary>Whether the study flag is set</summary>
        public bool Study;

        /// <summary>Whether the contingency must have come from CIM</summary>
        public bool FromCIM = false;

        /// <summary>The index/order of the element</summary>
        public int Index;

        /// <summary>The weather zones required for group membership</summary>
        public MM_Zone[] WeatherZones = new MM_Zone[] { };

        /// <summary>The load zones required for group membership</summary>
        public MM_Zone[] LoadZones = new MM_Zone[] { };

        /// <summary>The rdf:IDs required for group membership</summary>
        public MM_rdfID[] rdfIDs = new MM_rdfID[] { };

        /// <summary>The counties requred for group membership</summary>
        public MM_Boundary[] Counties = new MM_Boundary[] { };

        /// <summary>The KV levels requred for group membership</summary>
        public MM_KVLevel[] KVLevels = new MM_KVLevel[] { };

        /// <summary>The element types required for group membership</summary>
        public MM_Element_Type[] ElemTypes = new MM_Element_Type[] { };

        /// <summary>The groupings this group should contain</summary>
        public MM_Contingency_Grouping[] Groups = new MM_Contingency_Grouping[] { };

        /// <summary>The items that are defined by a successful parsing of elements</summary>
        public String[] FoundParse = new string[] { };

        /// <summary>The collection of substations to be excluded from this group</summary>
        public CIM_Element[] ExcludedStations = new CIM_Element[0];

        /// <summary>The collection of substations to be included in this group</summary>
        public CIM_Element[] AddedStations = new CIM_Element[0];
        #endregion

        #region Group validation
        /// <summary>
        /// Determine whether a contingency 
        /// </summary>
        /// <param name="cDef">The contingnecy definition</param>
        /// <param name="FullElements">The full collection of elements to be parsed through</param>
        /// <returns></returns>
        public bool ContingencyInGroup(MM_Contingency_Definition cDef, IEnumerable<CIM_Element> FullElements)
        {
            if (FromCIM && !cDef.FromCIM)
                return false;

            //If any of our explicitly excluded stations are referenced, remove them.
            if (ExcludedStations.Length > 0)
            {
                foreach (CIM_Element Elem in FullElements)
                    if (Elem.Type == "cim:ACLineSegment")
                    {
                        foreach (CIM_Element Substation in Elem.ACSegmentStations)
                            if (Array.IndexOf(ExcludedStations, Substation) != -1)
                                return false;
                    }
                    else if (Elem.Substation != null && Array.IndexOf(ExcludedStations, Elem.Substation) != -1)
                        return false;                                
            }
            

            if (Groups.Length > 0)
            {
                bool FoundGroup = false;
                foreach (MM_Contingency_Grouping cGroup in Groups)
                    if (cDef.Groups.Contains(cGroup))
                        FoundGroup = true;
                if (!FoundGroup)
                    return false;
            }

            

            if (WeatherZones.Length > 0)
            {
                bool FoundZone = false;
                foreach (CIM_Element Elem in cDef.ExportableElements.Keys)
                    if (!FoundZone && Elem.Substation != null && (Array.IndexOf<MM_Zone>(WeatherZones, MM_Repository.Zones[Elem.Substation["cim:SubGeographicalRegion>name"]])) != -1)
                        FoundZone = true;

                //Now, have the special handler for added elements
                if (!FoundZone && AddedStations.Length > 0)
                    foreach (CIM_Element Elem in FullElements)
                        if (Elem.Type == "cim:ACLineSegment")
                        {
                            foreach (CIM_Element Substation in Elem.ACSegmentStations)
                                if (Array.IndexOf(AddedStations, Substation) != -1)
                                    FoundZone = true;
                        }
                        else if (Elem.Substation != null && Array.IndexOf(AddedStations, Elem.Substation) != -1)
                            FoundZone = true;

                if (!FoundZone)
                    return false;
            }

            if (LoadZones.Length > 0)
            {
                bool FoundZone = false;
                foreach (CIM_Element Elem in cDef.ExportableElements.Keys)
                    if (!FoundZone && Elem.Substation != null && (Array.IndexOf<MM_Zone>(LoadZones, MM_Repository.Zones[Elem.Substation["etx:SettlementLoadZone>name"]])) != -1)
                        FoundZone = true;
                if (!FoundZone)
                    return false;
            }

            if (rdfIDs.Length > 0)
            {
                bool FoundRdfID = Array.IndexOf<MM_rdfID>(rdfIDs, this.rdfID) != -1;
                foreach (CIM_Element Elem in cDef.ExportableElements.Keys)
                    if (!FoundRdfID && Array.IndexOf<MM_rdfID>(rdfIDs, Elem.rdfID) != -1)
                        FoundRdfID = true;
                if (!FoundRdfID)
                    return false;
            }

            if (KVLevels.Length > 0 && ElemTypes.Length > 0 && ElemTypes[0].Name == "Line")
            {
                bool FoundKV = false;
                foreach (CIM_Element Elem in cDef.ExportableElements.Keys)
                    if (Elem.Type == "cim:ACLineSegment" && Array.IndexOf<MM_KVLevel>(KVLevels, MM_Database_CIM.LocateKVLevel(Elem.VoltageLevel)) != -1)
                        FoundKV = true;
                if (!FoundKV)
                    return false;
            }
            else if (KVLevels.Length > 0 && ElemTypes.Length > 0 && ElemTypes[0].Name == "Transformer")
            {
                bool FoundKV = false;
                foreach (CIM_Element Elem in cDef.ExportableElements.Keys)
                    if (Elem.Type == "cim:TransformerWinding" && WindingVoltageCount(KVLevels, Elem.Parse("cim:PowerTransformer")) >= 2)
                        FoundKV = true;
                if (!FoundKV)
                    return false;
            }
            else if (ElemTypes.Length > 0 && FoundParse.Length > 0)
            {
                bool FoundElem = false;
                foreach (CIM_Element Elem in FullElements)
                    if (Array.IndexOf<MM_Element_Type>(ElemTypes, MM_Database_CIM.LocateElementType(Elem)) != -1)
                        foreach (String str in FoundParse)
                            if (Elem.Parse(str) != null)
                                FoundElem = true;
                if (!FoundElem)
                    return false;
            }
            else if (ElemTypes.Length > 0)
            {
                bool FoundElemType = false;
                foreach (CIM_Element Elem in cDef.ExportableElements.Keys)
                    if (!FoundElemType && Array.IndexOf<MM_Element_Type>(ElemTypes, MM_Database_CIM.LocateElementType(Elem)) != -1)
                        FoundElemType = true;
                if (!FoundElemType)
                    return false;
            }

            if (Counties.Length > 0)
            {
                bool FoundCounty = false;
                foreach (CIM_Element Elem in cDef.ExportableElements.Keys)
                    if (!FoundCounty && Elem.Type == "cim:ACLineSegment" || Elem.Type == "cim:SeriesCompensator")
                    {
                        foreach (CIM_Element Sub in Elem.ACSegmentStations)
                            if (Array.IndexOf<MM_Boundary>(Counties, (MM_Database_CIM.LocateElement(Sub, MM_Repository.TEIDs) as MM_Substation).County) != -1)
                                FoundCounty = true;
                    }
                    else if (!FoundCounty)
                        if (Array.IndexOf<MM_Boundary>(Counties, (MM_Database_CIM.LocateElement(Elem.Substation, MM_Repository.TEIDs) as MM_Substation).County) != -1)
                            FoundCounty = true;

                //Now, have the special handler for added elements
                if (!FoundCounty && AddedStations.Length > 0)
                    foreach (CIM_Element Elem in FullElements)
                        if (Elem.Type == "cim:ACLineSegment")
                        {
                            foreach (CIM_Element Substation in Elem.ACSegmentStations)
                                if (Array.IndexOf(AddedStations, Substation) != -1)
                                    FoundCounty = true;
                        }
                        else if (Elem.Substation != null && Array.IndexOf(AddedStations, Elem.Substation) != -1)
                            FoundCounty = true;
                        
                if (!FoundCounty)
                    return false;
            }

            return true;
        }
        #endregion

        #region Transformer winding voltages
        /// <summary>
        /// Parse around a power transformer to pull in a list of all adjacent windings
        /// </summary>
        /// <param name="KVLevels">The KV Levels to be counted</param>
        /// <param name="SeedElement"></param>
        /// <returns></returns>
        private int WindingVoltageCount(MM_KVLevel[] KVLevels, CIM_Element SeedElement)
        {
            Dictionary<CIM_Element, CIM_Element> Windings = new Dictionary<CIM_Element, CIM_Element>();
            AddTransformer(Windings, SeedElement);
            
            int Count=0;
            foreach (CIM_Element VoltageLevel in Windings.Values)
                if (VoltageLevel != null && Array.IndexOf<MM_KVLevel>(KVLevels, MM_Database_CIM.LocateKVLevel(VoltageLevel)) != -1)
                    Count++;
            return Count;
        }

        /// <summary>
        /// Add a winding to a collection, and parse around it
        /// </summary>
        /// <param name="Windings">The collection of transformers and their windings</param>
        /// <param name="Transformer">The transformer to be added</param>
        private void AddTransformer(Dictionary<CIM_Element, CIM_Element> Windings, CIM_Element Transformer)
        {
            //If this transformer has already been parsed, return.
            if (Windings.ContainsKey(Transformer))
                return;

            //Otherwise, find our transformer windings
            Windings.Add(Transformer, null);
            foreach (CIM_Element XFw in Transformer.InLinks)
                if (XFw.Type == "cim:TransformerWinding")
                {
                    Windings.Add(XFw, XFw.VoltageLevel);
                    foreach (CIM_Element Node in XFw.Nodes)
                        if (Node.Type == "cim:ConnectivityNode")
                            foreach (CIM_Element OtherWinding in Node.Elements)
                                if (OtherWinding.Type == "cim:TransformerWinding" && !Windings.ContainsKey(OtherWinding))
                                    AddTransformer(Windings, OtherWinding.Parse("cim:PowerTransformer"));
                }
        }
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new contingency grouping
        /// </summary>
        /// <param name="xDef">The XML definition for the grouping</param>
        /// <param name="CtgGroups">Our list of contingency groups</param>
        /// <param name="Substations">Our collection of substations</param>
        public MM_Contingency_Grouping(XmlElement xDef, Dictionary<String, MM_Contingency_Grouping> CtgGroups, Dictionary<String, CIM_Element> Substations ):base(xDef)
        {
            if (xDef.HasAttribute("Groups"))
            {
                List<MM_Contingency_Grouping> Groups = new List<MM_Contingency_Grouping>();
                foreach (String GroupToFind in xDef.Attributes["Groups"].Value.Split(','))
                    Groups.Add(CtgGroups[GroupToFind]);
                this.Groups = Groups.ToArray();
            }
            if (xDef.HasAttribute("AddedStations"))
                this.AddedStations = RetrieveSubstations(xDef.Attributes["AddedStations"].Value.Split(','), Substations);
            if (xDef.HasAttribute("ExcludedStations"))
                this.ExcludedStations = RetrieveSubstations(xDef.Attributes["ExcludedStations"].Value.Split(','), Substations);           

        }

        /// <summary>
        /// Retrieve a collection of substations
        /// </summary>
        /// <param name="SubNames"></param>
        /// <param name="Substations"></param>
        /// <returns></returns>
        private CIM_Element[] RetrieveSubstations(string[] SubNames, Dictionary<string, CIM_Element> Substations)
        {
            CIM_Element[] OutSubs = new CIM_Element[SubNames.Length];
            for (int a = 0; a < SubNames.Length; a++)
                OutSubs[a] = Substations[SubNames[a]];
            return OutSubs;
        }
        #endregion

        #region Exporting
        /// <summary>
        /// Write out a contingency grouping
        /// </summary>
        /// <param name="xW"></param>
        public void WriteGrouping(XmlTextWriter xW)
        {
            xW.WriteStartElement("etx:ContingencyGroup");
            xW.WriteAttributeString("rdf:ID", this.rdfID.ToString());            
            xW.WriteElementString("cim:IdentifiedObject.name", this.Name);
            xW.WriteElementString("cim:IdentifiedObject.aliasName", "");
            if (this.Description.Length > 28)
                xW.WriteElementString("cim:IdentifiedObject.description", this.Description.Substring(0,28));
            else
                xW.WriteElementString("cim:IdentifiedObject.description", this.Description);
            xW.WriteElementString("cim:IdentifiedObject.localName", "");
            xW.WriteElementString("cim:IdentifiedObject.mRID", MM_Exporter_Contingencies.GetMRID().ToString());
            xW.WriteElementString("cim:IdentifiedObject.pathName", "");
            xW.WriteElementString("etx:ContingencyGroup.study", XmlConvert.ToString(Study));
            xW.WriteEndElement();
        }

        /// <summary>
        /// Export this group to an XML file
        /// </summary>
        /// <param name="TargetDirectory">The directory in which the file should be exported</param>
        /// <param name="Definitions">The collection of contingency definitoins</param>
        public void Export(String TargetDirectory, Dictionary<String, MM_Contingency_Definition> Definitions)
        {
            using (XmlTextWriter xW = new XmlTextWriter(TargetDirectory + "\\" + Description.Replace('/','-') + ".xml", Encoding.UTF8))
            {
                xW.Formatting = Formatting.Indented;
                

                //First, write our contingency element
                WriteGrouping(xW);
                

                //Now, go through all contingency elements linked with this one, and export the group memeber and elements                  
                foreach (MM_Contingency_Definition cDef in Definitions.Values)
                    if (cDef.Groups.Contains(this))
                    {
                        WriteGroupMember(xW, cDef);
                        cDef.WriteDefinition(xW);
                    }                                                                
            }        
        }

        /// <summary>
        /// Write out the contingency group member - linkage between the group and contingency definition
        /// </summary>
        /// <param name="xW"></param>
        /// <param name="cDef"></param>
        public void WriteGroupMember(XmlTextWriter xW, MM_Contingency_Definition cDef)
        {
            xW.WriteStartElement("etx:ContingencyGroupMember");
            xW.WriteAttributeString("rdf:ID", "_" + Guid.NewGuid().ToString("B").ToUpper());
            xW.WriteElementString("cim:IdentifiedObject.name", cDef.Name.ToUpper());
            xW.WriteElementString("cim:IdentifiedObject.aliasName", "");
            xW.WriteElementString("cim:IdentifiedObject.description", cDef.Description);
            xW.WriteElementString("cim:IdentifiedObject.localName", "");
            xW.WriteElementString("cim:IdentifiedObject.mRID", MM_Exporter_Contingencies.GetMRID().ToString());
            xW.WriteElementString("cim:IdentifiedObject.pathName", "");

            xW.WriteStartElement("etx:ContingencyGroupMember.Contingency");
            xW.WriteAttributeString("rdf:resource", "#" + cDef.rdfID.ToString());
            xW.WriteEndElement();

            xW.WriteStartElement("etx:ContingencyGroupMember.ContingencyGroup");
            xW.WriteAttributeString("rdf:resource", "#" + this.rdfID.ToString());
            xW.WriteEndElement();

            xW.WriteEndElement();
        }
        #endregion

        #region Conversion
        /// <summary>
        /// Report an easier-to-read string
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this.Description;
        }
        #endregion
    }
}
